/**
 * Project Lantern - Quest Api
 * @version 1.0.0
 * @license EPL-2.0
 */
import {
  RawMessage,
  world,
  Player,
  ItemStack,
  Container,
} from "@minecraft/server";
import {
  ActionFormData,
  ActionFormResponse,
  MessageFormData,
} from "@minecraft/server-ui";
import { utils } from "../..";

/**
 * Namespace of Quest Complete Tag.
 */
let questNameSpace: string = "quest";

/**
 * Create a Quest Book with Chapters.
 * @beta
 */
export class ChapterQuestBook {
  /**
   * The unique id of the QuestBook.
   */
  readonly id: string;
  /**
   * Title of the  QuestBook.
   */
  title: string | RawMessage;
  /**
   * Body of the QuestBook.
   */
  protected body: string | RawMessage;
  /**
   * Chapters of the Questbook.
   */
  chapters: QuestBook[];
  /**
   * Display the book to the player.
   * @param player
   */
  display(player: Player): void {
    const mainForm: ActionFormData = new ActionFormData()
      .title(this.title)
      .body(this.body);
    this.chapters.forEach((chapter: QuestBook) => {
      mainForm.button(chapter.title, chapter.iconPath);
    });
    mainForm.show(player).then((response: ActionFormResponse) => {
      if (response.canceled || response.selection === undefined) {
        return;
      }
      this.chapters[response.selection].display(player);
    });
  }
  constructor(
    id: string,
    title: string | RawMessage,
    body: string | RawMessage,
    chapters: QuestBook[],
  ) {
    this.id = id;
    this.title = title;
    this.body = body;
    this.chapters = chapters;
    world.afterEvents.itemUse.subscribe((event) => {
      if (event.itemStack.typeId === this.id) this.display(event.source);
    });
  }
}

/**
 * Create a quest book.
 * @example Please add these strings to your language file.
 * ~~~
 * api.unexpected_error.quest=任务系统出现未知错误
 * quest.finished=你完成了 %1 任务！
 * quest.done=任务已完成
 * quest.check=尝试提交任务
 * quest.not_enough.item=材料不足，你需要 %1 个 %2 才能完成这个任务
 * quest.not_enough.level=你需要 %1 级才能完成这个任务
 * quest.not_enough.xp=你需要 %1 经验才能完成这个任务
 * ~~~
 * @example addCustomQuestBook.ts
 * ~~~ts
 * setQuestNameSpace("example_quest");
 * 
 * // define the quest.
 * const EXAMPLE_QUEST: Quest = new Quest(
 * "example_quest", 
 * "Example Quest", 
 * "This is an example quest",
 * { item: { name: "Apple" item: new ItemStak("apple")} }, 
 * { playerXpLevel: 1})
 * 
 * // define the quest book.
 * const QUEST_BOOK: QuestBook = new QuestBook(
 * "example_quest_book"
 * "Example Quest Book", 
 * "This is an example quest book",
 *  [EXAMPLE_QUEST]
  )
 * ~~~
 * @beta
 */
export class QuestBook {
  /**
   * The unique id of the QuestBook.
   */
  readonly id: string;
  /**
   * Title of the  QuestBook.
   */
  title: string | RawMessage;
  /**
   * Body of the QuestBook.
   */
  protected body: string | RawMessage;
  /**
   * Quests of the Questbook.
   */
  private readonly quests: Quest[];
  /**
   * If true, the quest book will not display when player use the item.
   */
  isChapter?: boolean;
  /**
   * The icon of the Quest.
   * It should be the path from the root of the resource pack.
   * @example texture/gui/example_pic
   */
  iconPath?: string;
  /**
   * Display the book to the player.
   * @param player
   */
  display(player: Player): void {
    const mainForm: ActionFormData = new ActionFormData()
      .title(this.title)
      .body(this.body);
    this.quests.forEach((quest: Quest) => {
      mainForm.button(
        quest.title + (quest.isCompleted(player) ? " §2✔" : ""),
        quest.iconPath,
      );
    });
    mainForm.show(player).then((response: ActionFormResponse) => {
      if (response.canceled || response.selection === undefined) {
        return;
      }
      this.quests[response.selection].display(player, this);
    });
  }
  constructor(
    id: string,
    title: string | RawMessage,
    body: string | RawMessage,
    quests: Quest[],
    isChapter = false,
    iconPath?: string,
  ) {
    this.id = id;
    this.title = title;
    this.body = body;
    this.quests = quests;
    this.isChapter = isChapter;
    this.iconPath = iconPath;
    if (isChapter === true) {
      world.afterEvents.itemUse.subscribe((event) => {
        if (event.itemStack.typeId === this.id) this.display(event.source);
      });
    }
  }
  /**
   * Add a quest.
   * @param quest
   * @param message Optional information will be sent to world.
   */
  addQuest(quest: Quest, message?: string | RawMessage): void {
    this.quests.push(quest);
    if (typeof message === "string") {
      world.sendMessage({ text: message });
      return;
    }
    if (message) {
      world.sendMessage(message);
      return;
    }
  }
  /**
   * Get the quest by id.
   * @param id
   */
  getQuest(id: string): Quest | undefined {
    return this.quests.find((quest: Quest) => quest.id === id);
  }
  /**
   * Get all quests.
   */
  getQuests(): Quest[] {
    return this.quests;
  }
}

/**
 * Create a Quest.
 * @example addCustomQuest.ts
 * ~~~ts
 * setQuestNameSpace("example_quest");
 *
 * // define the quest.
 * const EXAMPLE_QUEST: Quest = new Quest(
 * "example_quest",
 * "Example Quest",
 * "This is an example quest",
 * { item: { name: "Apple" item: new ItemStak("apple")} },
 * { playerXpLevel: 1})
 * ~~~
 * @beta
 */
export class Quest {
  /**
   * The unique id of the Quest.
   */
  readonly id: string;
  /**
   * Title of the  QuestBook.
   */
  protected _title: string | RawMessage;
  /**
   * Body of the  QuestBook.
   */
  protected _body: string | RawMessage;
  /**
   * Condition to complete the quest.
   */
  condition: QuestCondition;
  /**
   * It will be called when the Quest is completed by the player.
   */
  award: QuestAward;
  /**
   * The icon of the Quest.
   * It should be the path from the root of the resource pack.
   * @example texture/gui/example_pic
   */
  iconPath?: string;
  /**
   * Form of the quest.
   */
  private form: MessageFormData;
  /**
   * Let a player complete the quest and give award.
   * @param player
   */
  complete(player: Player): void {
    player.addTag(`${questNameSpace}:${this.id}`);
    player.addLevels(this.award.playerXpLevel ?? 0);
    player.addExperience(this.award.playerXpPoint ?? 0);
    let itemAward: ItemStack = this.award?.item?.item ?? new ItemStack("air");
    utils.giveItem([player], itemAward);
    player.playSound("random.levelup");
    player.sendMessage({
      translate: "quest.finished",
      with: typeof this.title === "string" ? [this.title] : this.title,
    });
  }
  /**
   * Show ui to a player.
   * @param player
   * @param book
   */
  display(player: Player, book?: QuestBook): void {
    if (this.isCompleted(player)) {
      this.form
        .title(this.title)
        .body(this.body)
        .button1({ translate: "gui.back" })
        .button2({ translate: "quest.done" });
    } else {
      this.form
        .title(this.title)
        .body(this.body)
        .button1({ translate: "gui.back" })
        .button2({ translate: "quest.check" });
    }
    const CONTAINER = player.getComponent("minecraft:inventory")
      ?.container as Container;
    this.form.show(player).then((response) => {
      if (
        response.canceled ||
        response.selection === undefined ||
        response.selection === 0 ||
        this.isCompleted(player)
      ) {
        book?.display(player);
        return;
      } else if (response.selection === 1) {
        if (checkCondition(this.condition, player)) this.complete(player);
      }
    });
  }
  constructor(
    id: string,
    title: string | RawMessage,
    body: string | RawMessage,
    condition: QuestCondition,
    award: QuestAward,
    iconPath?: string,
  ) {
    this.id = id;
    this._title = title;
    this._body = body;
    this.iconPath = iconPath;
    this.condition = condition;
    this.award = award;
    this.form = new MessageFormData();
  }
  /**
   * Check if a player has completed this quest.
   * @param player
   */
  isCompleted(player: Player): boolean {
    return player.hasTag(`${questNameSpace}:${this.id}`);
  }
  set body(content: string | RawMessage) {
    this._body = content;
    this.form.body(content);
  }
  set title(content: string | RawMessage) {
    this._title = content;
    this.form.title(content);
  }
  get body() {
    return this._body;
  }
  get title() {
    return this._title;
  }
}

/**
 * Check if a player is up to the condition.
 * @param condition
 * @param player
 * @returns
 */
function checkCondition(condition: QuestCondition, player: Player): boolean {
  let message: RawMessage = { rawtext: [] };
  const CONTAINER: undefined | Container = player.getComponent(
    "minecraft:inventory",
  )?.container;
  if (!CONTAINER) {
    world.sendMessage({ translate: "api.unexpected_error.quest" }); // An unexpected error occurred!
    return false;
  }
  if (
    condition.item &&
    utils.getItemAmountInContainer(CONTAINER, condition.item.item.typeId) <
      condition.item.item.amount
  ) {
    message.rawtext?.push({
      rawtext: [
        {
          translate: "quest.not_enough.item",
          with: [condition.item.item.amount.toString(), condition.item.name],
        }, // Following items are missing from your inventory:
        {
          text: "\n",
        },
      ],
    });
  }

  if (condition.playerXpLevel && player.level < condition.playerXpLevel) {
    message.rawtext?.push({
      rawtext: [
        {
          translate: "quest.not_enough.level",
          with: [player.level.toString()],
        }, // You need %%1 more level(s)!
        {
          text: "\n",
        },
      ],
    });
  }
  if (
    condition.playerXpPoint &&
    player.getTotalXp() < condition.playerXpPoint
  ) {
    message.rawtext?.push({
      rawtext: [
        {
          translate: "quest.not_enough.xp",
          with: [player.getTotalXp.toString()],
        }, // You need %%1 more experience!
        {
          text: "\n",
        },
      ],
    });
  }
  player.sendMessage(message);
  return <number>message.rawtext?.length > 0 ? false : true;
}

export interface QuestCondition {
  /**
   * Match only typeId and min amount.
   */
  item?: ItemData;
  /**
   * The specific level will be required to unlock the quest.
   */
  playerXpLevel?: number;
  /**
   * The specific point will be required to unlock the quest.
   */
  playerXpPoint?: number;
}

export interface QuestAward {
  /**
   * Player will get these items when the quest is finished.
   */
  item?: ItemData;
  /**
   * The specific level will be given to the player.
   */
  playerXpLevel?: number;
  /**
   * The specific point will be given to the player.
   */
  playerXpPoint?: number;
}

/**
 * Item data.
 * @beta
 */
export interface ItemData {
  /**
   * Name of the item.
   */
  name: string;
  /**
   * The item.
   */
  item: ItemStack;
}

/**
 * Set the namespace of Quest Complete Tag
 * @param str the namespace
 */
export function setQuestNameSpace(str: string) {
  questNameSpace = str;
}
